package cn.jhz.learn.community_dynamic.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import cn.jhz.learn.community_dynamic.dao.PermissionJpaRepository;
import cn.jhz.learn.community_dynamic.dao.RoleJpaRepository;
import cn.jhz.learn.community_dynamic.model.AclEntity;
import cn.jhz.learn.community_dynamic.model.RoleEntity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PermissionPreProcessor implements ApplicationListener<ContextRefreshedEvent> {

    private final PermissionJpaRepository permissionJpaRepository;
    private final RoleJpaRepository roleJpaRepository;
    private final RequestMappingHandlerMapping requestMappingHandlerMapping;
    private final List<AclEntity> resourcesList;
    
//    private static final BiFunction<String, String, Boolean> matches = (name1, name2)->Pattern.matches(".*" + name1 + ".*", name2);
    private static final Function<String, String> adpt = (val) ->{
	Matcher matcher = Pattern.compile("\\(\\'.*\\'\\)").matcher(val);	
	matcher.find();
	return matcher.group().replaceAll("[\\(\\)\\']", "");
    };

    @Autowired
    public PermissionPreProcessor(PermissionJpaRepository permissionJpaRepository, RoleJpaRepository roleJpaRepository, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.permissionJpaRepository = permissionJpaRepository;
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
        this.roleJpaRepository = roleJpaRepository;
        resourcesList = permissionJpaRepository.findByType("API");
    }


    /**
     * 将系统中所有权限表达式加载进入数据库 0:从数据库中查询出所有权限表达式，然后对比，如果已经存在了，跳过，不存在添加
     * 1:获取controller中所有带有@RequestMapper标签的方法 2:遍历所有方法，判断当前方法是否贴有@PreAuthorize权限控制标签
     * 3:如果有，解析得到权限表达式，封装成Permission对象保存到Permission表中,
     * 
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
	Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
	Collection<HandlerMethod> methods = handlerMethods.values();

	List<AclEntity> newList = new ArrayList<>();
	for (HandlerMethod method : methods) {
	    if (method.hasMethodAnnotation(PreAuthorize.class)) {
		if (!permissionContain(method, resourcesList)) {
		    String name = adpt.apply(method.getMethodAnnotation(PreAuthorize.class).value());
		    Optional<String> resource = getResource(method);
		    newList.add(new AclEntity(name, "/api/" + resource.orElse(name), "API", 0, (byte) 1));
		}
	    }
	}

	permissionJpaRepository.saveAll(newList);
	if (newList.size() > 0) {
	    RoleEntity superAdmin = this.roleJpaRepository.findByName("SuperAdmin").get();
	    superAdmin.getAcls().addAll(newList);
	    this.roleJpaRepository.saveAndFlush(superAdmin);
	}
	// TODO 过滤掉非API权限
	resourcesList.addAll(newList);
    }
    
    private boolean permissionContain(HandlerMethod method, List<AclEntity> permissions) {
	for (AclEntity permission : permissions) {
	    if(adpt.apply(method.getMethodAnnotation(PreAuthorize.class).value()).equals(permission.getName()))
		return true;
	}
	return false;
    }

    public List<AclEntity> getResourcesList() {
        return resourcesList;
    }
    
    private Optional<String> getResource(HandlerMethod method) {
	if (method.hasMethodAnnotation(RequestMapping.class)) {
	    return Optional.ofNullable(method.getMethodAnnotation(RequestMapping.class).value()[0]);
	}
	return Optional.empty();
    }
    
    
}
